/*******************************************************************************
 * Copyright 2010 Simon Mieth
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/

package org.kabeja.entities.util;

import java.util.Stack;
import java.util.StringTokenizer;

import org.kabeja.entities.MText;
import org.kabeja.entities.Text;

/**
 * @author <a href="mailto:simon.mieth@gmx.de>Simon Mieth</a>
 * 
 */
public class TextParser {
	public static TextDocument parseMText(MText text) {
		// initialize
		TextDocument doc = new TextDocument();
		StringBuffer buf = new StringBuffer();
		StringBuffer value = new StringBuffer();
		StyledTextParagraph p = new StyledTextParagraph();
		p.setFontHeight(text.getHeight());
		p.setInsertPoint(text.getInsertPoint());

		switch (text.getAlignment()) {
		case MText.ATTACHMENT_TOP_LEFT:
			p.setValign(StyledTextParagraph.VERTICAL_ALIGNMENT_TOP);

			break;

		case MText.ATTACHMENT_TOP_CENTER:
			p.setValign(StyledTextParagraph.VERTICAL_ALIGNMENT_TOP);

			break;

		case MText.ATTACHMENT_TOP_RIGHT:
			p.setValign(StyledTextParagraph.VERTICAL_ALIGNMENT_TOP);

			break;

		case MText.ATTACHMENT_MIDDLE_LEFT:
			p.setValign(StyledTextParagraph.VERTICAL_ALIGNMENT_CENTER);

			break;

		case MText.ATTACHMENT_MIDDLE_CENTER:
			p.setValign(StyledTextParagraph.VERTICAL_ALIGNMENT_CENTER);

			break;

		case MText.ATTACHMENT_MIDDLE_RIGHT:
			p.setValign(StyledTextParagraph.VERTICAL_ALIGNMENT_CENTER);

			break;

		case MText.ATTACHMENT_BOTTOM_LEFT:
			p.setValign(StyledTextParagraph.VERTICAL_ALIGNMENT_BASELINE);

			break;

		case MText.ATTACHMENT_BOTTOM_CENTER:
			p.setValign(StyledTextParagraph.VERTICAL_ALIGNMENT_BASELINE);

			break;

		case MText.ATTACHMENT_BOTTOM_RIGHT:
			p.setValign(StyledTextParagraph.VERTICAL_ALIGNMENT_BASELINE);

			break;

		default:
			p.setValign(StyledTextParagraph.VERTICAL_ALIGNMENT_BASELINE);

			break;
		}

		boolean formatting = false;
		boolean keyfollow = false;
		boolean complete = true;
		Stack paras = new Stack();
		int linecount = 0;
		String str = text.getText();
		char key = ' ';

		// parse the symbols
		str = parseSymbols(str);

		// parse the style
		for (int i = 0; i < str.length(); i++) {
			char c = str.charAt(i);

			switch (c) {
			case '\\':

				if (formatting) {
					if (!complete) {
						parseStyledTextParagraphSettings(key, value.toString(),
								p);
						value.delete(0, value.length());
						formatting = true;
						keyfollow = true;
					} else {
						buf.append(c);
						formatting = false;
					}
				} else {
					formatting = true;
					keyfollow = true;
					complete = false;
				}

				break;

			case '~':

				if (formatting) {
					buf.append(c);
					formatting = false;
					keyfollow = false;
				}

				break;

			case ';':

				if (formatting) {
					parseStyledTextParagraphSettings(key, value.toString(), p);
					value.delete(0, value.length());
					formatting = false;
					complete = true;
					keyfollow = false;
				} else {
					buf.append(c);
				}

				break;

			case '}':

				if (formatting && keyfollow) {
					buf.append(c);
					formatting = false;
					keyfollow = false;
				} else {
					if (formatting) {
						value.append(c);
					} else {
						// format change end
						doc.addStyledParagraph(p);
						p = createParagraphFromParent(p);

						if (paras.size() > 0) {
							p = (StyledTextParagraph) paras.pop();
						}
					}
				}

				break;

			case '{':

				if (formatting && keyfollow) {
					buf.append(c);
					formatting = false;
					keyfollow = false;
				} else {
					if (formatting) {
						value.append(c);
					} else {
						// start format change
						if (i != 0) {
							paras.add(p);
							p = new StyledTextParagraph();
						}
					}
				}

				break;

			case 'O':

				if (formatting && keyfollow) {
					p.setText(buf.toString());
					buf.delete(0, buf.length());
					doc.addStyledParagraph(p);
					p = createParagraphFromParent(p);
					p.setOverline(true);
					formatting = false;
					keyfollow = false;
				} else {
					if (formatting) {
						value.append(c);
					} else {
						buf.append(c);
					}
				}

				break;

			case 'o':

				if (formatting && keyfollow) {
					p.setText(buf.toString());
					buf.delete(0, buf.length());
					doc.addStyledParagraph(p);
					p = createParagraphFromParent(p);
					p.setOverline(false);
					formatting = false;
					keyfollow = false;
				} else {
					if (formatting) {
						value.append(c);
					} else {
						buf.append(c);
					}
				}

				break;

			case 'u':

				if (formatting && keyfollow) {
					p.setText(buf.toString());
					buf.delete(0, buf.length());
					doc.addStyledParagraph(p);
					p = createParagraphFromParent(p);
					p.setUnderline(false);
					formatting = false;
					keyfollow = false;
				} else {
					if (formatting) {
						value.append(c);
					} else {
						buf.append(c);
					}
				}

				break;

			case 'L':

				if (formatting && keyfollow) {
					p.setText(buf.toString());
					buf.delete(0, buf.length());
					doc.addStyledParagraph(p);
					p = createParagraphFromParent(p);
					p.setUnderline(true);
					formatting = false;
					keyfollow = false;
				} else {
					if (formatting) {
						value.append(c);
					} else {
						buf.append(c);
					}
				}

				break;

			case 'l':

				if (formatting && keyfollow) {
					p.setText(buf.toString());
					buf.delete(0, buf.length());
					doc.addStyledParagraph(p);
					p = createParagraphFromParent(p);
					p.setUnderline(false);
					formatting = false;
					keyfollow = false;
				} else {
					if (formatting) {
						value.append(c);
					} else {
						buf.append(c);
					}
				}

				break;

			case 'P':

				if (formatting && keyfollow) {
					linecount++;
					p.setText(buf.toString());
					buf.delete(0, buf.length());

					doc.addStyledParagraph(p);
					p = createParagraphFromParent(p);

					formatting = false;
					keyfollow = false;
					p.setLineIndex(linecount);
					p.setNewline(true);
				} else {
					if (formatting) {
						value.append(c);
					} else {
						buf.append(c);
					}
				}

				break;

			default:

				if (formatting) {
					if (keyfollow) {
						key = c;
						keyfollow = false;
					} else {
						value.append(c);
					}
				} else {
					buf.append(c);
				}

				break;
			}
		}

		if (formatting) {
			parseStyledTextParagraphSettings(key, value.toString(), p);
		}

		if (buf.length() > 0) {
			p.setText(buf.toString());
			doc.addStyledParagraph(p);
		}

		if (doc.getParagraphCount() == 0) {
			doc.addStyledParagraph(p);
		}

		return doc;
	}

	protected static StyledTextParagraph createParagraphFromParent(
			StyledTextParagraph parent) {
		StyledTextParagraph p = new StyledTextParagraph();
		p.setValign(parent.getValign());
		p.setBold(parent.isBold());
		p.setFont(parent.getFont());
		p.setItalic(parent.isItalic());
		p.setUnderline(parent.isUnderline());
		p.setOverline(parent.isOverline());
		p.setWidth(parent.getWidth());
		p.setFontHeight(parent.getFontHeight());
		p.setInsertPoint(parent.getInsertPoint());

		return p;
	}

	public static TextDocument parseText(Text text) {
		TextDocument doc = new TextDocument();

		// boolean asciicontrol = false;
		StringBuffer buf = new StringBuffer();

		StyledTextParagraph p = new StyledTextParagraph();
		p.setFontHeight(text.getHeight());
		p.setInsertPoint(text.getAlignmentPoint());

		switch (text.getValign()) {
		case Text.VALIGN_BASELINE:

			if (text.getAlign() == Text.ALIGN_MIDDLE) {
				// described in the DXF specs
				p.setValign(StyledTextParagraph.VERTICAL_ALIGNMENT_CENTER);
			} else {
				p.setValign(StyledTextParagraph.VERTICAL_ALIGNMENT_BASELINE);
			}

			break;

		case Text.VALIGN_BOTTOM:
			p.setValign(StyledTextParagraph.VERTICAL_ALIGNMENT_BASELINE);

			break;

		case Text.VALIGN_CENTER:
			p.setValign(StyledTextParagraph.VERTICAL_ALIGNMENT_CENTER);

			break;

		case Text.VALIGN_TOP:
			p.setValign(StyledTextParagraph.VERTICAL_ALIGNMENT_TOP);

			break;
		}

		if ((text.getAlign() == 3) || (text.getAlign() == 5)) {
			double length = Utils.distance(text.getInsertPoint(), text
					.getAlignmentPoint());
			p.setWidth(length);
		}

		// parse the symbols
		String str = parseSymbols(text.getText());

		// parse the style
		int marker = 0;
		char c;

		// initialize
		boolean overline = false;
		boolean underline = false;

		for (int i = 0; i < str.length(); i++) {
			c = str.charAt(i);

			if (c == '%') {
				marker++;
			} else {
				if (marker == 0) {
					buf.append(c);
				} else if (marker == 2) {
					switch (c) {
					case 'o':
						p.setText(buf.toString());
						p.setUnderline(underline);
						p.setOverline(overline);
						doc.addStyledParagraph(p);
						p = createParagraphFromParent(p);
						buf.delete(0, buf.length());
						overline = !overline;
						p.setOverline(overline);

						break;

					case 'u':
						p.setText(buf.toString());
						p.setUnderline(underline);
						p.setOverline(overline);
						doc.addStyledParagraph(p);
						p = createParagraphFromParent(p);

						buf.delete(0, buf.length());
						underline = !underline;

						p.setUnderline(underline);

						break;
					}

					marker = 0;
				}
			}
		}

		if ((marker == 1) || (marker == 3)) {
			buf.append('%');
		}

		// something left over?
		if (buf.length() > 0) {
			p.setText(buf.toString());
			doc.addStyledParagraph(p);
		}

		return doc;
	}

	public static void parseStyledTextParagraphSettings(char key, String value,
			StyledTextParagraph para) {
		if (value.length() > 0) {
		
			switch (key) {
			case 'A':
				para.setValign(Integer.parseInt(value));

				break;

			case 'H':

				if (value.endsWith("x")) {
					para.setFontHeight(para.getFontHeight()
							* Double.parseDouble(value.substring(0, value
									.length() - 1)));
				} else {
					para.setFontHeight(Double.parseDouble(value));
				}

				break;

			case 'Q':
				para.setObliquiAngle(Double.parseDouble(value));

				break;

			case 'W':
				if (value.endsWith("x")) {
					double widthFactor = Double.parseDouble(value.substring(0,
							value.length() - 1));
					para.setWidth(para.getWidth() * widthFactor);
				} else {
					para.setWidth(Double.parseDouble(value));
				}

				break;

			case 'T':
				para.setCharacterspace(Double.parseDouble(value));

				break;

			case 'f':
				parseFontSettings(value.trim(), para);

				break;

			case 'F':
				para.setFontFile(value.trim());

				break;

			case 'S':

				// TODO handle
				break;
			}
		}
	}

	public static void parseFontSettings(String value, StyledTextParagraph para) {
		StringTokenizer st = new StringTokenizer(value, "|");
		para.setFont(st.nextToken());

		while (st.hasMoreTokens()) {
			String option = st.nextToken();
			char code = option.charAt(0);
			int i = Integer.parseInt(option.substring(1));

			switch (code) {
			case 'b':
				para.setBold(i == 1);

				break;

			case 'i':
				para.setItalic(i == 1);

				break;

			case 'c':

				// ??? color?
				break;

			case 'p':

				// ???
				break;
			}
		}
	}

	public static String parseSymbols(String text) {
		boolean asciicontrol = false;
		StringBuffer buf = new StringBuffer();
		int marker = 0;
		char c;

		for (int i = 0; i < text.length(); i++) {
			c = text.charAt(i);

			if (c == '%') {
				if (marker == 2) {
					// a sequence of %%%%%065 means '%A'
					buf.append('%');
					marker = 0;
				} else {
					marker++;
				}
			} else if (c == '^') {
				asciicontrol = true;
			} else if (asciicontrol) {
				// ASCII-control sign map
				if (Character.isWhitespace(c)) {
					buf.append('^');
				} else {
					// filtering acsii controls here
				}

				asciicontrol = false;
			} else if (c == '\\') {
				// test for unicode
				if ((text.length() > (i + 6)) && (text.charAt(i + 1) == 'U')
						&& (text.charAt(i + 2) == '+')) {
					String part = text.substring(i + 3, i + 7);
					int unicode = Integer.parseInt(part, 16);
					buf.append((char) unicode);
					i += 6;
				} else {
					buf.append('\\');
				}
			} else {
				if (marker == 0) {
					buf.append(c);
				} else if (marker == 1) {
					// the control % self
					buf.append('%');
					marker = 0;
				} else if (marker == 2) {
					switch (c) {
					case 'd':
						buf.append('\u00B0');

						break;

					case 'c':
						buf.append('\u2205');

						break;

					case 'p':
						buf.append('\u00B1');

						break;

					default:

						if (Character.isDigit(c) && ((i + 2) < text.length())) {
							String code = "" + c + text.charAt(i + 1)
									+ text.charAt(i + 2);

							try {
								c = (char) Integer.parseInt(code);
								buf.append(c);
								i += 2;
							} catch (NumberFormatException e) {
								// TODO sometimes there is only one
								// digit, so what should be the
								// replacement???
								buf.append('?');
								i++;
							}
						} else {
							// a style control write back
							buf.append("%%");
							buf.append(c);
						}
					}

					marker = 0;
				} else if (marker == 3) {
					buf.append('%');
					marker = 0;
				}
			}
		}

		if ((marker == 1) || (marker == 3)) {
			buf.append('%');
		}

		return buf.toString();
	}
}
